<#
.SYNOPSIS
RemoteCommand 模块一键安装脚本（支持PS 5.1+）

.EXAMPLE
PS> .\Install-RemoteCommand.ps1
#>

# 配置参数
$ModuleName = "RemoteCommand"
$MinimumPSVersion = [version]"5.1"
$RecommendedPSVersion = [version]"7.0"

# 检查管理员权限
$isAdmin = ([Security.Principal.WindowsPrincipal][Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)

# 检查PowerShell版本
if ($PSVersionTable.PSVersion -lt $MinimumPSVersion) {
    Write-Host "❌ 需要 PowerShell $MinimumPSVersion 或更高版本 (当前版本: $($PSVersionTable.PSVersion))" -ForegroundColor Red
    exit 1
}

# 显示推荐版本提示
if ($PSVersionTable.PSVersion -lt $RecommendedPSVersion) {
    Write-Host @"
========================================================
⚠️ 建议升级到 PowerShell 7.0+ 以获得：
   - 更好的并发性能
   - 空值合并运算符支持
   - 增强的管道处理
   下载地址：https://aka.ms/install-powershell
========================================================`n
"@ -ForegroundColor Yellow
}

# 定义模块内容
$ModuleCode = @'
function Invoke-RemoteCommand {
    <#
    .SYNOPSIS
    增强版通用远程命令执行器，支持并行控制、智能重试和多种输出格式
    
    .DESCRIPTION
    该函数提供以下核心功能：
    - 支持脚本块和字符串命令输入
    - 自动重试机制和动态超时调整
    - 并发执行控制（PowerShell 7+）
    - 预执行检查与自定义状态映射
    - 多种输出格式（JSON/XML/CSV）和异步作业模式
    
    .PARAMETER ComputerNames
    目标计算机名称列表（支持管道输入）
    
    .PARAMETER Command
    要执行的 PowerShell 脚本块或命令字符串
    
    .PARAMETER TimeoutSeconds
    初始超时时间（秒），默认30秒
    
    .PARAMETER Credential
    远程执行凭据
    
    .PARAMETER RetryCount
    失败重试次数，默认0次不重试
    
    .PARAMETER JobName
    自定义作业名称
    
    .PARAMETER ThrottleLimit
    最大并发数（仅PowerShell 7+有效），默认5
    
    .PARAMETER OutputType
    输出格式（None/JSON/XML/CSV），默认None
    
    .PARAMETER SuccessOnly
    仅返回成功结果
    
    .PARAMETER AsJob
    异步作业模式
    
    .PARAMETER PreCheckScript
    预检查脚本块，默认测试WS-Man连接
    
    .PARAMETER StatusMap
    自定义状态分类规则（哈希表）
    
    .EXAMPLE
    # 基本用法
    Invoke-RemoteCommand -ComputerNames "PC01","PC02" `
        -Command "Get-Service WinRM" `
        -ThrottleLimit 3
    
    .EXAMPLE
    # 异步作业模式
    $job = Invoke-RemoteCommand -ComputerNames (1..10) `
        -Command { Get-Process } `
        -AsJob
    $job | Wait-Job | Receive-Job
    
    .EXAMPLE
    # 自定义预检查
    $check = { Test-Connection $args -Count 1 -Quiet }
    Invoke-RemoteCommand -ComputerNames "SRV01" `
        -PreCheckScript $check `
        -Command "Update-Help -Force"
    #>
    [CmdletBinding(DefaultParameterSetName = "Default")]
    param (
        [Parameter(Mandatory = $true, ValueFromPipeline = $true)]
        [string[]]$ComputerNames,

        [Parameter(Mandatory = $true)]
        [object]$Command,

        [int]$TimeoutSeconds = 30,
        [System.Management.Automation.PSCredential]$Credential,
        [int]$RetryCount = 0,
        [string]$JobName,

        [ValidateRange(1, 100)]
        [int]$ThrottleLimit = 5,

        [ValidateSet("JSON", "XML", "CSV", "None")]
        [string]$OutputType = "None",

        [switch]$SuccessOnly,

        [switch]$AsJob,

        [string]$PreCheckScript = "Test-WSMan -ComputerName {0} -ErrorAction SilentlyContinue",

        [hashtable]$StatusMap = @{
            "^TIMEOUT"    = "Timeout"
            "^拒绝访问"   = "AccessDenied"
            "无法解析"    = "DNSFailure"
            "DEFAULT"     = "OtherError"
        }
    )

    begin {
        # 兼容性处理
        if ($PSVersionTable.PSVersion.Major -lt 7 -and $ThrottleLimit -gt 1) {
            Write-Warning "并发控制需要PowerShell 7.0+，当前使用顺序执行"
            $ThrottleLimit = 1
        }

        # 命令类型处理
        if ($Command -is [scriptblock]) {
            $Command = $Command.ToString()
        }
        elseif ($Command -isnot [string]) {
            throw "Command 参数必须为字符串或脚本块"
        }

        # 动态超时跟踪表
        # $timeoutTracker = @{}

        # 预处理超时参数
        # $preparedArgs = foreach ($computer in $ComputerNames) {
        #     @{
        #         Computer  = $computer
        #         Timeout   = if ($timeoutTracker.ContainsKey($computer)) {
        #                         $timeoutTracker[$computer]
        #                     } else {
        #                         $TimeoutSeconds
        #                     }
        #     }
        # }

        enum ResultOfExecution {
            Success
            Error
            Timeout
            Failed
        }
    }

    process {
        # 处理并行执行
        $results = $ComputerNames | ForEach-Object -ThrottleLimit $ThrottleLimit -Parallel {
            $computer = $_
            $maxAttempts = $using:RetryCount + 1
            $attempt = 0
            $currentTimeout = $using:TimeoutSeconds
            $job = $null
            $output = $null
            $errorOutput = $null

            # 创建作业参数
            $jobParams = @{
                ComputerName = $computer
                ScriptBlock  = [scriptblock]::Create($using:Command)
                AsJob        = $true
                ErrorAction  = 'Stop'
            }
            if ($using:Credential) { $jobParams['Credential'] = $using:Credential }
            if ($using:JobName) { $jobParams['Name'] = "$using:JobName-$computer" }

            do {
                $attempt++
                try {
                    # 预检查
                    $preCheckPass = [scriptblock]::Create($using:PreCheckScript -f $computer)
                    if (-not (& $preCheckPass)) {
                        $errorOutput = "预检查失败"
                        break
                    }

                    # 启动作业
                    $job = Invoke-Command @jobParams
                    Write-LogInternal -Message "[$computer][ATTEMPT]开始第 $attempt 次尝试（超时: ${currentTimeout}s）"

                    # 等待作业
                    $jobCompleted = $job | Wait-Job -Timeout $currentTimeout
                    # 作业结束
                    if ($jobCompleted) {
                        $output = Receive-Job $job -ErrorVariable remoteError 2>&1
                        if ($remoteError) {
                            $errorOutput = $remoteError.Exception.Message
                        }
                        break
                    }
                    $job | Stop-Job -PassThru | Remove-Job -Force
                    Write-LogInternal -Message "[$computer][ATTEMPT]第 $attempt 次失败"
                }
                catch {
                    Write-Error $_
                    Write-Error $_.InvocationInfo.commandorigin
                    # Write-Error $_.InvocationInfo.displayscriptposition
                    Write-Error $_.InvocationInfo.invocationname
                    Write-Error $_.InvocationInfo.line
                    Write-Error $_.InvocationInfo.mycommand
                    Write-Error $_.InvocationInfo.pscommandpath
                    Write-Error $_.InvocationInfo.psscriptroot
                    Write-Error $_.InvocationInfo.scriptname
                    Write-Error $_.InvocationInfo.statement
                    Write-Error $_.InvocationInfo
                    Write-Error $_.InvocationInfo.ScriptLineNumber
                    Write-Error $_.InvocationInfo.OffsetInLine
                    Write-Error $_.Exception
                    Write-Error $_.Exception.Message
                    # $errorOutput = "${$_.InvocationInfo.ScriptLineNumber}行，${$_.InvocationInfo.OffsetInLine}列 ${$_.Exception.Message}"
                    $errorOutput = $_.Exception.Message
                    break
                }
                finally {
                    if ($job) { $job | Remove-Job -Force }
                }

                # 更新超时策略
                $currentTimeout = $currentTimeout + 5
            } while ($attempt -lt $maxAttempts)

            # 生成结果对象
            [PSCustomObject]@{
                ComputerName = $computer
                Status       = $null
                Output       = $output -join "`n"
                Error        = $errorOutput
                Attempts     = $attempt
                Timestamp    = Get-Date -Format "yyyy-MM-dd HH:mm:ss"
                Latency      = if ($job) { $job.PSEndTime - $job.PSBeginTime } else { $null }
            }
        }

        # 状态分类
        foreach ($result in $results) {
            # foreach ($key in $StatusMap.Keys) {
            #     if ($result.Error -match $key) {
            #         $result.Status = $StatusMap[$key]
            #     } elseif ($StatusMap.ContainsKey("DEFAULT")) {
            #         $result.Status = $StatusMap["DEFAULT"]
            #     } else {
            #         $result.Status = "OTHER"
            #     }
            # }
            if ($result.Attempts > 1) {
                $result.Status = [ResultOfExecution]::TIMEOUT
            } elseif ($result.Error) {
                $result.Status = [ResultOfExecution]::ERROR
            } elseif ($result.Output -ne $null) {
                $result.Status = [ResultOfExecution]::SUCCESS
            } else {
                $result.Status = [ResultOfExecution]::FAILED
            }
        }

        # 结果过滤
        $filteredResults = if ($SuccessOnly) {
            $results | Where-Object { $_.Status -eq [ResultOfExecution]::SUCCESS }
        }
        else {
            $results
        }

        # 输出格式化
        $formattedResults = switch ($OutputType) {
            "JSON" { $filteredResults | ConvertTo-Json -Depth 5 }
            "XML"  { $filteredResults | ConvertTo-Xml -As String -Depth 5 }
            "CSV"  { $filteredResults | ConvertTo-Csv -NoTypeInformation }
            default { $filteredResults }
        }

        # 异步作业处理
        if ($AsJob) {
            $jobObject = Register-ObjectEvent -InputObject $formattedResults `
                -EventName Completed `
                -Action { 
                    param($source) 
                    Write-Output $source.Sender 
                } -MessageData $formattedResults
            
            return $jobObject
        }
        else {
            return $formattedResults
        }
    }

    end {
        # 清理残留作业
        Get-Job -Name "$JobName-*" -ErrorAction SilentlyContinue | Remove-Job -Force
    }
}

function Write-LogInternal {
    <#
    .SYNOPSIS
    日志函数
    
    .DESCRIPTION
    打印日志，并且写入日志文件。
    
    .PARAMETER Message
    消息内容

    .PARAMETER Level
    消息级别：'Info','Warning','Error'
    
    .PARAMETER LogPath
    日志文件路径，默认：用户文档目录\RemoteCommand.log

    .EXAMPLE
    WriteLogInternal -Message "xxx" -Level "Info" -LogPath "C:\Logs\RemoteCommands.log"
    #>
    param(
        [Parameter(Mandatory)]
        [string]$Message,
        [ValidateSet('Info','Warning','Error')]
        [string]$Level = 'Info',
        [string]$LogPath = "$([Environment]::GetFolderPath('MyDocuments'))\RemoteCommand.log"
    )
    
    begin {
        # 初始化日志目录
        $logDir = Split-Path $LogPath -Parent
        if (-not (Test-Path $logDir)) {
            New-Item -Path $logDir -ItemType Directory -Force | Out-Null
        }
    }

    process {
        # 生成时间戳和日志条目
        $timestamp = Get-Date -Format "yyyy-MM-dd HH:mm:ss"
        $logEntry = "[$timestamp][$Level] $Message"
        # 使用 Mutex 确保线程安全写入
        $mutexName = "Global\PowerShellLogMutex"
        $mutex = New-Object System.Threading.Mutex($false, $mutexName)
        try {
            if ($mutex.WaitOne(1000)) {
                Write-Verbose $logEntry
                $logEntry | Out-File -FilePath $LogPath -Append -Encoding UTF8
            }
        } finally {
            $mutex.ReleaseMutex()
            $mutex.Dispose()
        }
    }
}
'@

# 模块安装路径
$ModulePath = if ($isAdmin) {
    "$env:ProgramFiles\WindowsPowerShell\Modules\$ModuleName"
} else {
    "$HOME\Documents\WindowsPowerShell\Modules\$ModuleName"
}

try {
    # 创建模块目录
    $null = New-Item -Path $ModulePath -ItemType Directory -Force -ErrorAction Stop

    # 生成模块文件
    $Manifest = @{
        RootModule           = "$ModuleName.psm1"
        ModuleVersion        = "1.2.0"
        Author               = "Theo Zhong"
        Description          = "增强版远程命令执行模块"
        PowerShellVersion    = "7.0"
        CompatiblePSEditions = @('Desktop', 'Core')
        FunctionsToExport    = @('Invoke-RemoteCommand', 'Write-LogInternal')
    }

    # 保存PSM1文件
    $ModuleCode | Out-File "$ModulePath\$ModuleName.psm1" -Encoding UTF8

    # 生成模块清单
    New-ModuleManifest -Path "$ModulePath\$ModuleName.psd1" @Manifest

    # 验证安装
    $InstalledVersion = Get-Module -ListAvailable $ModuleName | 
                      Sort-Object Version -Descending | 
                      Select-Object -First 1 -ExpandProperty Version

    Write-Host "✅ 安装成功！" -ForegroundColor Green
    Write-Host @"
    
    模块信息：
    名称:    $ModuleName
    版本:    $($InstalledVersion.ToString())
    路径:    $ModulePath
    命令:    Invoke-RemoteCommand

    使用方式：
    1. 导入模块: Import-Module $ModuleName -Force
    2. 查看帮助: Get-Help Invoke-RemoteCommand -Full
    3. 卸载命令: Remove-Item '$ModulePath' -Recurse -Force

    注意：首次使用可能需要设置执行策略：
    Set-ExecutionPolicy RemoteSigned -Scope CurrentUser
"@

}
catch {
    Write-Host "❌ 安装失败: $_" -ForegroundColor Red
    # 清理残留文件
    if (Test-Path $ModulePath) {
        Remove-Item $ModulePath -Recurse -Force
    }
    exit 2
}
